import { BaseComp } from '../../../common/component/comp';
import { ZRenderComp } from '../../../common/component/zrComp';
import { parseColorText } from '../../../common/color/color'

import { loadFont, paths, colors, layouts } from './layouts';

import { Programme } from '../../compute/programme2019';

/**
 * ProgrammeView
 * ├── Background (包括 top / bottom)
 * ├── Header
 * ├── Contents (多个节目的整体, 以及两个节目单之间的 spliter)
 * │   ├── ProgrammeItem (单个节目单)
 */

class ProgrammeView extends BaseComp {
  constructor(args) {
    super(null);
    this.context = args.context;
    this.zrender = args.zrender;
    /** @type {HTMLElement} */ this.dom = args.dom;
    this.zr = this.zrender.init(this.dom);
    this.defer(() => this.zr.dispose());

    this.loadingFinished = false;
    loadFont('FangZheng', this.context)
      .then(() => this.afterLoading())

    /** @type {Programme[]} */
    this.programmes = [];
  }

  afterLoading() {
    this.loadingFinished = true;
    this.bg = new Background({
      parent : this,
      zrender : this.zrender,
      zr : this.zr,
    });
    this.header = new Header({
      parent : this,
      zrender : this.zrender,
      zr : this.zr,
      x : layouts.header.x, y : layouts.header.y,
    });
    this.contents = new Contents({
      parent : this,
      zrender : this.zrender,
      zr : this.zr,
      x : layouts.contents.x, y : layouts.contents.y,
    });

    this.setProgrammes(this.programmes);
  }

  /**
   * @param {Programme[]} programmes 
   */
  setProgrammes(ps) {
    this.programmes = ps;
    this.contents && this.contents.setProgrammes(ps);
  }
}

class Background extends ZRenderComp {
  constructor(args) {
    super(args.parent, args.zrender);
    this.useExistZRenderInstance(args.zr, 0, 0);
  }

  init() {
    this.createPathFromString(paths.top)
      .fill(colors.uiMain)
      .z(0, 101)
      .build();
    this.createPathFromString(paths.bottom)
      .fill(colors.uiSub)
      .z(0, 101)
      .build();

    this.createPathFromString(paths.topPanel)
      .fillLinearGradient(0, 0, 0, 1, [
        {offset:0, color:colors.bgD1},
        {offset:1, color:colors.bgNormal}
      ])
      .z(0, 100)
      .build();
    this.createPathFromString(paths.bottomPanel)
      .fillLinearGradient(0, 0, 0, 1, [
        {offset:0, color:colors.bgNormal},
        {offset:1, color:colors.bgD1}
      ])
      .z(0, 100)
      .build();

    const topLayout = layouts.bg.top;
    this.topBars = new Array(4).fill().map((_, index) =>
      this.createPathFromString(paths.topBar)
        .fillLinearGradient(0, 0, 0, 1, [
          {offset:0, color:colors.uiMainD1},
          {offset:1, color:colors.uiMain}
        ])
        .position(topLayout.barX + index * topLayout.barOffX, topLayout.barY)
        .z(0, 102)
        .build()
    );
    this.ticker((now) => {
      let delta = (now % 4000) / 1000;
      if (delta < 1) {
        this.topBars[0].opacity(1);
        this.topBars[1].opacity(1);
        this.topBars[2].opacity(0);
        this.topBars[3].opacity((1 - delta % 1) * 0.6);
      } else if (delta < 2) {
        this.topBars[1].opacity(1);
        this.topBars[2].opacity(1);
        this.topBars[3].opacity(0);
        this.topBars[0].opacity((1 - delta % 1) * 0.6);
      } else if (delta < 3) {
        this.topBars[2].opacity(1);
        this.topBars[3].opacity(1);
        this.topBars[0].opacity(0);
        this.topBars[1].opacity((1 - delta % 1) * 0.6);
      } else {
        this.topBars[3].opacity(1);
        this.topBars[0].opacity(1);
        this.topBars[1].opacity(0);
        this.topBars[2].opacity((1 - delta % 1) * 0.6);
      }
    })

    // 背景部分
    this.blurBgImage().then(canvas => {
      this.createImage(canvas)
        .z(0, -11)
        .build();
    });

    this.blackStripes().then(canvas => {
      this.spStrips = this.createImage(canvas)
        .z(0, -10)
        .build();
      this.ticker((now) => {
        this.spStrips.moveTo(0, - (now % 5000) / 500);
      });
    });

    const xx1 = layouts.global.width * 0.8;
    const xx2 = layouts.global.width * 0.87;
    const xx3 = layouts.global.width * 0.757;
    this.spBlackTx1s = new Array(3).fill().map(() => this.createText('接下来', 140)
      .textStyle('left', 'top', '700', 'Microsoft YaHei UI')
      .textFill('#0003')
      .rotation(Math.PI / 2)
      .z(0, -9)
      .build());
    this.spBlackTx2s = new Array(9).fill().map(() => this.createText('NEXT', 66)
      .textStyle('left', 'top', '700', 'Impact')
      .textFill('#0003')
      .rotation(Math.PI / 2)
      .z(0, -9)
      .build());
    this.spBlackTx3s = new Array(9).fill().map(() => this.createText('NEXT', 66)
      .textStyle('left', 'top', '700', 'Impact')
      .textFill('#0003')
      .rotation(Math.PI / 2)
      .z(0, -9)
      .build());
    const txW1 = this.spBlackTx1s[0].getBoundingRect().width;
    const txW2 = this.spBlackTx2s[0].getBoundingRect().width;
    const txW3 = txW2;
    this.ticker((now) => {
      let offset1 = (now % 5000) / 5000 * txW1 * 1.2;
      let offset2 = (now % 8000) / 8000 * txW2 * 1.2;
      let offset3 = (now % 8000) / 8000 * txW3 * 1.2 + txW3 / 2;
      this.spBlackTx1s.forEach((sp, index) => {
        sp.moveTo(xx1, index * (txW1 * 1.2) + offset1);
      });
      this.spBlackTx2s.forEach((sp, index) => {
        sp.moveTo(xx2, (index + 1) * (txW2 * 1.2) - offset2);
      });
      this.spBlackTx3s.forEach((sp, index) => {
        sp.moveTo(xx3, (index + 1) * (txW2 * 1.2) - offset3);
      });
    });
  }

  blurBgImage() {
    return new Promise((resolve, reject) => {
      let img = document.createElement('img');
      img.onload = () => {
        resolve(img);
      }
      img.onerror = (e) => reject(e);
      img.src = this.getContext().assetPath('img/TheAetherealGates.jpg');
    }).then((/** @type {HTMLImageElement} */ img) => {
      let canvas = document.createElement('canvas');
      canvas.width = layouts.global.width;
      canvas.height = layouts.global.height;

      let ctx = canvas.getContext('2d');
      ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
      let data = ctx.getImageData(0, 0, canvas.width, canvas.height);
      let array = data.data;

      const w = data.width;
      const h = data.height;
      let get = (x, y, offset) => {
        x = x <= 0 ? 0 : x >= w ? w : x;
        y = y <= 0 ? 0 : y >= h ? h : y;
        return array[(y * w + x) * 4 + offset];
      }

      const blurMap = [
        [1, 1, 1, 1, 1],
        [1, 3, 3, 3, 1],
        [1, 3, 8, 3, 1],
        [1, 3, 3, 3, 1],
        [1, 1, 1, 1, 1],
      ];
      const blurOffX = 2;
      const blurOffY = 2;
      const blurAll = blurMap.reduce((p, v) => p + v.reduce((p2, v2) => p2 + v2, 0), 0);
      let blur = (x, y, offset) => {
        let sum = 0;
        for (let xx = 0; xx < blurMap.length; xx++) {
          for (let yy = 0; yy < blurMap[0].length; yy++) {
            sum += (get(x + xx - blurOffX, y + yy - blurOffY, offset)) * blurMap[yy][xx];
          }
        }
        return sum / blurAll;
      }

      let ndata = new ImageData(w, h);
      let narray = ndata.data;

      for (let x = 0; x < w; x++) {
        for (let y = 0; y < h; y++) {
          let i = (y * w + x) * 4;
          narray[i] = blur(x, y, 0);
          narray[i + 1] = blur(x, y, 1);
          narray[i + 2] = blur(x, y, 2);
          narray[i + 3] = 255;
        }
      }
      ctx.putImageData(ndata, 0, 0);
      return canvas;
    });
  }

  blackStripes() {
    let canvas = document.createElement('canvas');
    const w = canvas.width = (layouts.global.width * 0.6) | 0;
    const h = canvas.height = layouts.global.height + 10;
    let ctx = canvas.getContext('2d');

    let data = new ImageData(w, h);
    let array = data.data;
    for (let x = 0; x < w; x++) {
      let ax = 1-x/w;
      for (let y = 0; y < h; y++) {
        let i = (y * w + x) * 4;
        array[i] = 0;
        array[i + 1] = 0;
        array[i + 2] = 0;
        array[i + 3] = (255 * ax * Math.abs((y % 10) / 5 - 1) * 0.5) | 0;
      }
    }
    ctx.putImageData(data, 0, 0);
    return Promise.resolve(canvas);
  }
}

class Header extends ZRenderComp {
  constructor(args) {
    super(args.parent, args.zrender);
    this.useExistZRenderInstance(args.zr, args.x, args.y);
  }

  init() {
    this.createPathFromString(paths.nextChJie)
      .fill(colors.bgL2)
      .shadow('#000', 5, 3, 3)
      .position(40, 0)
      .z(0, 30)
      .build();
    this.createPathFromString(paths.nextChXia)
      .fill(colors.bgL2)
      .shadow('#000', 5, 3, 3)
      .scale(0.6)
      .position(140, 25)
      .z(0, 30)
      .build();
    this.createPathFromString(paths.nextChLai)
      .fill(colors.bgL2)
      .shadow('#000', 5, 3, 3)
      .scale(0.6)
      .position(190, 25)
      .z(0, 30)
      .build();

    this.arrows = this.createPolyline(...new Array(24).fill().map(() => [0, 0]))
      .fill(colors.uiMain)
      .stroke('#000', 0)
      .z(0, 29)
      .build();
    this.ticker(now => this.tickArrows(now));
  }

  tickArrows(now) {
    let offXs = [10, 25, 0];
    let offYs = [34, 54, 74];
    const delta = now % 1000;

    let poses = [
      // 最左边部分
      [offXs[0], offYs[0]],
      [offXs[1], offYs[1]],
      [offXs[2], offYs[2]],
      [345 + offXs[2], offYs[2]],
      [345 + offXs[1], offYs[1]],
      [345 + offXs[0], offYs[0]],
    ];

    for (let i = 0; i < 3; i++) {
      let xx = 345 + i * 50 + (delta / 20);
      let ww = 60 - i * 20 - (delta / 1000 * 20);

      poses.push(
        [xx + offXs[0] - ww, offYs[0]],
        [xx + offXs[1] - ww, offYs[1]],
        [xx + offXs[2] - ww, offYs[2]],
        [xx + offXs[2], offYs[2]],
        [xx + offXs[1], offYs[1]],
        [xx + offXs[0], offYs[0]],
      );
    }

    this.arrows.polylineShape()
      .pointList(poses)
      .update();
  }
}

class Contents extends ZRenderComp {
  constructor(args) {
    super(args.parent, args.zrender);
    this.useExistZRenderInstance(args.zr, args.x, args.y);

    /** @type {ProgrammeItem[]} */
    this.items = [];
    this.spSpliter = [];
  }

  init() {
    // do nothing
  }

  /**
   * @param {Programme[]} programmes 
   */
  setProgrammes(programmes) {
    this.items.forEach(i => i.destroy());
    this.spSpliter.forEach(sp => sp.destroy());
    this.spSpliter = [];

    if (programmes.length == 0) {
      return;
    }

    this.items = programmes.map((p, index) => {
      const i = new ProgrammeItem({
        parent : this,
        zrender : this.zrender,
        zr : this.zr,
        x : 0, index,
        y : index * layouts.contents.programme.height,
        programme : p,
        afterInit : () => {
          if (this.items.some(ii => !ii.hasInited)) {
            return;
          }
          this.afterItemInited();
        }
      });
      return i;
    });
  }

  afterItemInited() {
    const maxPrefixWidth = this.items.reduce((p, i) => i.prefixWidth > p ? i.prefixWidth : p, 0);
    const maxTitleWidth = this.items.reduce((p, i) => i.titleWidth > p ? i.titleWidth : p, 0);

    const layout = layouts.contents.programme;
    const titleX = layout.prefixOffX + maxPrefixWidth + layout.prefixMarginRight;
    const suffixX = titleX + maxTitleWidth + layout.titleMarginRight;
    this.items.forEach(i => i.setTitleX(titleX, suffixX));

    // 画 spliter
    this.spSpliter = new Array(this.items.length + 1).fill().map((_, index) => {
      const yy = index * layout.height;
      return this.createPathFromString('M0-6l6,6l-6,6l-6-6Z')
        .fill(colors.uiMain)
        .position(0, yy)
        .z(0, 10)
        .build();
    })
  }
}

/**
 * 因为所有节目 item 需要进行对齐, 所以刚开始产生 item, 所有的组件是透明的看不见.
 * 需要父组件 Contents 进行对齐.
 */
class ProgrammeItem extends ZRenderComp {
  constructor(args) {
    super(args.parent, args.zrender);
    this.useExistZRenderInstance(args.zr, args.x, args.y);

    /** @type {Programme} */
    this.programme = args.programme;
    /** @type {function} */
    this.afterInit = args.afterInit;

    /** @type {number} */
    this.index = args.index;
    this.prefixWidth = 0;
    this.titleWidth = 0;
  }

  init() {
    const layout = layouts.contents.programme;
    const ox = layout.bgX;
    const oy = layout.bgY;
    const ow = layout.bgWidth;
    const oh = layout.bgHeight;
    const oc = layout.bgCorner;
    const { programme:p } = this;

    this.bg = this.createPathFromString(`M${ox+oc},${oy}l-${oc},${oc}V${oy+oh-oc}l${oc},${oc}H${ox+ow}V${oy}Z`)
      .fillLinearGradient(0, 0, 1, 0, [
        {offset: 0.3, color: colors.uiMain },
        {offset: 1,   color: parseColorText(colors.uiMain).setA(0).toHexRGBA() },
      ])
      .opacity(this.index % 2 == 1 ? 0.3 : 0.15)
      .z(0, 10)
      .build();

    this.prefix = this.createText(p.prefix, layout.prefixFont)
      .textFill('#FFF')
      .textStroke(colors.uiSub, 2)
      .textStyle('left', 'center', '500', '"Impact", "Microsoft YaHei UI"', 'italic')
      .position(layout.prefixOffX, layout.height / 2)
      .z(0, 12)
      .build();
    this.prefixWidth = this.prefix.getBoundingRect().width;

    // title
    let hasSubTitle = p.subtitles.length > 0;
    this.title = this.createText(p.title, layout.titleFont)
      .textFill('#FFF')
      .textStyle('left', hasSubTitle ? 'bottom' : 'center', '500', 'FangZheng', 'italic')
      .position(0, hasSubTitle ? layout.height / 2 + layout.titleFont / 4 : layout.height / 2)
      .textShadow('#000', 3, 5, 3)
      .z(0, 12)
      .build();
    let w1 = this.title.getBoundingRect().width;

    let w2 = 0;
    this.subtitles = p.subtitles.map((str, index) => {
      let sp = this.createText(str, layout.subtitleFont)
        .textFill(index % 2 == 0 ? colors.textMain : colors.textSub)
        .textStyle('left', 'top', '500', 'FangZheng', 'italic')
        .position(0, layout.height / 2 + layout.subtitleFont * 0.6)
        .textShadow('#000', 3, 5, 3)
        .z(0, 12)
        .build();
      w2 += (sp.getBoundingRect().width + layout.subtitleMargin);
      return sp;
    });

    this.titleWidth = Math.max(w1, w2);

    // suffix
    if (p.suffix.length == 0) {
      this.suffix = [];
    } else if (p.suffixType == 'normal') {
      this.suffix = [
        this.createText(p.suffix[0], layout.suffixFont)
          .textFill('#FFF')
          .textStyle('left', 'center', '700', 'Microsoft YaHei UI')
          .position(0, layout.height / 2)
          .textShadow('#000', 3, 5, 3)
          .z(0, 12)
          .build()
      ];
    } else {
      this.suffix = [];
      for (let i = 0; i < p.suffix.length; i++) {
        let s = p.suffix[i];
        let sp = this.createText(s, layout.suffixFont)
          .textFill('#FFF')
          .textStyle('left', 'center', '700', 'Microsoft YaHei UI')
          .position(0, layout.height / 2)
          .textShadow('#000', 3, 5, 3)
          .z(0, 12)
          .build();
        this.suffix.push(sp);

        if (p.suffix.length - 1 == i) break;

        sp = this.createText('vs', layout.suffixSplitFont)
          .textFill(colors.textSub)
          .textStyle('left', 'center', '300', '"Microsoft Himalaya", "Arial"')
          .position(0, layout.height / 2)
          .textShadow('#000', 3, 5, 3)
          .build();
        this.suffix.push(sp);
      }
    }

    // end
    this.afterInit();
  }

  setTitleX(titleX, suffixX) {
    const layout = layouts.contents.programme;
    this.title.moveTo(titleX);

    let xx = titleX;
    this.subtitles.forEach(sp => {
      sp.moveTo(xx);
      xx += sp.getBoundingRect().width + layout.subtitleMargin;
    });

    xx = suffixX;
    this.suffix.forEach(sp => {
      sp.moveTo(xx);
      xx += sp.getBoundingRect().width + layout.suffixMargin;
    });
  }
}

export {
  ProgrammeView,
};
